#include "LinkedList.h"

static void destroyNode(LinkedList* list, LLNode* node);
static LLNode* createNewNode();

LinkedList* createLinkedList(compareFunction cmpr, copyFunction copy, freeFunction free) {
    LinkedList* res = NULL;
    if (cmpr != NULL) {
        res = (LinkedList*) malloc(sizeof (LinkedList));
        if (res != NULL) {
            res->head = NULL;
            res->tail = NULL;
            res->size = 0;
            res->cmpr = cmpr;
            res->copy = copy;
            res->free = free;
        }
    }
    return res;
}

void LLDestroy(LinkedList * list) {
    LLNode * curr = list->head;
    while (curr != NULL) {
        LLNode* next = curr->next;
        destroyNode(list, curr);
        curr = next;
    }
    free(list);
}

static void destroyNode(LinkedList* list, LLNode* node) {
    if (list->free != NULL) {
        list->free(node->data);
    }
    free(node);
}

unsigned LLSize(LinkedList * list) {
    return list->size;
}

int LLIsEmpty(LinkedList * list) {
    return list->size == 0;
}

LLNode* LLFind(LinkedList * list, void* itm) {
    LLNode* res = NULL;
    if (list != NULL && itm != NULL) {
        compareFunction cmpr = list->cmpr;
        LLNode * curr = list->head;
        for (; curr != NULL; curr = curr->next) {
            if (cmpr(itm, curr->data) == 0) {
                res = curr;
                break;
            }
        }
    }
    return res;
}

static LLNode* LLFindFirstEqualOrGreater(LinkedList * list, void* itm) {
    LLNode* res = NULL;
    if (list != NULL && itm != NULL) {
        compareFunction cmpr = list->cmpr;
        LLNode * curr = list->head;
        for (; curr != NULL; curr = curr->next) {
            if (cmpr(itm, curr->data) <= 0) {
                res = curr;
                break;
            }
        }
    }
    return res;
}

LLNode* LLAdd(LinkedList * list, void* itm) {
    LLNode* res = createNewNode();
    if (res != NULL) {
        res->data = itm;
        if (list->copy != NULL) {
            res->data = list->copy(itm);
        }
        if (list->tail == NULL) {
            list->head = list->tail = res;
        } else {
            res->prev = list->tail;
            list->tail->next = res;
            list->tail = res;
        }
        list->size++;
    }
    return res;
}

static LLNode* createNewNode() {
    LLNode* res = (LLNode*) malloc(sizeof (LLNode));
    if (res != NULL) {
        res->data = NULL;
        res->next = NULL;
        res->prev = NULL;
    }
    return res;
}

int LLRemove(LinkedList * list, void* itm) {
    int res = 0;
    if (list != NULL && itm != NULL) {
        LLNode* toDelete = LLFind(list, itm);
        if (toDelete != NULL) {
            LLRemoveNode(list, toDelete);
            res = 1;
        }
    }
    return res;
}

void LLRemoveNode(LinkedList * list, LLNode* node) {
    if (list != NULL && node != NULL) {
        list->size--;
        if (node->prev == NULL) {
            list->head = node->next;
            if (list->size == 0) {
                list->tail = NULL;
            } else {
                list->head->prev = NULL;
            }
        } else {
            if (list->tail == node) {
                list->tail = node->prev;
            }
            node->prev->next = node->next;
        }
        destroyNode(list, node);
    }
}
